package com.lzj.framework.security;

import cn.hutool.core.util.ObjectUtil;
import com.lzj.common.core.domain.model.LoginUser;
import com.lzj.common.helper.SecurityHelper;
import com.lzj.common.security.service.TokenService;
import com.lzj.common.utils.spring.SpringUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import reactor.util.annotation.Nullable;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 贱贱
 * @Description <b>基于JWT-token的认证过滤器</b>
 * <p>该过滤器继承{@link OncePerRequestFilter}，确保每个请求只被过滤一次。</p>
 * <p>该过滤器在{@link UsernamePasswordAuthenticationFilter}之前执行，以便在用户名和密码认证之前进行JWT-token的验证。</p>
 * @Date 2024/07/14 17:42
 */
//@Component Filter类型的组件会被注册为spring过滤器,这样这个过滤器会存在两个地方(spring过滤器链和security过滤器链)
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
    private final TokenService tokenService;

    public JwtAuthenticationTokenFilter() {
        // 因为过滤器不交给spring管理,所以这里使用SpringUtils获取组件
        this.tokenService = SpringUtils.getBean(TokenService.class);
    }

    @Override
    protected void doFilterInternal(
        @Nullable HttpServletRequest request, 
        @Nullable HttpServletResponse response, 
        @Nullable FilterChain filterChain ) throws ServletException, IOException {

        // 从请求携带的token中获取登录信息
        LoginUser loginUser = tokenService.getLoginUser(request);
        
        // 如果登录信息不为空 并且安全上下文中没有数据
        // 将登录信息存入安全上下文,以便在之后的操作中从上下文中取出用户登录信息
        if (ObjectUtil.isNotNull(loginUser) && ObjectUtil.isNull(SecurityHelper.getAuthentication())) {
            tokenService.verifyToken(loginUser);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityHelper.setAuthentication(authenticationToken);
        }
        assert filterChain != null;
        filterChain.doFilter(request, response);

    }
}
